App.require(['quaternion', 'matrix4', "math"], function (quaternion, matrix4, _Math) {
    var Quaternion = quaternion.Quaternion, Matrix4 = matrix4.Matrix4;
    function Vector3(x, y, z) {
        this.x = x || 0;
        this.y = y || 0;
        this.z = z || 0;
    }
    Vector3.prototype = {
        constructor: Vector3,
    
        set: function (x, y, z) {
            this.x = x;
            this.y = y;
            this.z = z;
            return this;
        },
    
        clone: function () {
            return new this.constructor( this.x, this.y, this.z );
        },
        lerp: function (a, b, t) {
            return Vector3.lerp(a, b, t, this);
        },
    
        copy: function (v) {
            this.x = v.x;
            this.y = v.y;
            this.z = v.z;
            return this;
        },
    
        add: function (v) {
            this.x += v.x;
            this.y += v.y;
            this.z += v.z;
            return this;
        },
    
        addScalar: function (s) {
            this.x += s;
            this.y += s;
            this.z += s;
            return this;
        },
    
        addScaledVector: function (v, s) {
            this.x += v.x * s;
            this.y += v.y * s;
            this.z += v.z * s;
            return this;
        },
    
        sub: function (v) {
            this.x -= v.x;
            this.y -= v.y;
            this.z -= v.z;
            return this;
        },
    
        subScalar: function (s) {
            this.x -= s;
            this.y -= s;
            this.z -= s;
            return this;
        },
    
        multiply: function (v) {
            this.x *= v.x;
            this.y *= v.y;
            this.z *= v.z;
            return this;
        },
    
        multiplyScalar: function (s) {
            this.x *= s;
            this.y *= s;
            this.z *= s;
            return this;
        },
    
        applyEuler: function (euler) {
            this.quaternion = this.quaternion || new Quaternion();
            return this.applyQuaternion(this.quaternion.setFromEuler(euler));
        },
    
        applyAxisAngle: function (axis, angle) {
            this.quaternion = this.quaternion || new Quaternion();
            return this.applyQuaternion(this.quaternion.setFromAxisAngle(axis, angle));
        },
    
        applyMatrix3: function (m) {
            var x = this.x, y = this.y, z = this.z;
            var e = m.elements;
            this.x = e[0] * x + e[3] * y + e[6] * z;
            this.y = e[1] * x + e[4] * y + e[7] * z;
            this.z = e[2] * x + e[5] * y + e[8] * z;
            
            return this;
        },
    
        applyMatrix4: function (m) {
            var x = this.x, y = this.y, z = this.z;
            var e = m.elements;
            
            var w = 1 / (e[3] * x + e[7] * y + e[11] * z + e[15]);
            this.x = (e[0] * x + e[4] * y + e[8] * z + e[12]) * w;
            this.y = (e[1] * x + e[5] * y + e[9] * z + e[13]) * w;
            this.z = (e[2] * x + e[6] * y + e[10] * z + e[14]) * w;
    
            return this;
        },
    
        applyMatrix4v: function (m) {
            var e = m.elements, x = this.x, y = this.y, z = this.z;
            this.x = x * e[0] + y * e[4] + z * e[ 8] + e[11];
            this.y = x * e[1] + y * e[5] + z * e[ 9] + e[12];
            this.z = x * e[2] + y * e[6] + z * e[10] + e[13];
            return this;
        },
    
        applyQuaternion: function (q) {
            var x = this.x, y = this.y, z = this.z;
            var qx = q.x, qy = q.y, qz = q.z, qw = q.w;
    
            var ix = qw * x + qy * z - qz * y;
            var iy = qw * y + qz * x - qx * z;
            var iz = qw * z + qx * y - qy * x;
            var iw = - qx * x - qy * y - qz * z;
    
            this.x = ix * qw + iw * - qx + iy * - qz - iz * - qy;
            this.y = iy * qw + iw * - qy + iz * - qx - ix * - qz;
            this.z = iz * qw + iw * - qz + ix * - qy - iy * - qx;
    
            return this;
        },
    
        project: function (matrix, viewport) {
            this.applyMatrix4(matrix);
            this.x = (this.x * 0.5 + 0.5) * viewport[2] + viewport[0];
            this.y = (this.y * 0.5 + 0.5) * viewport[3] + viewport[1];
            this.z = (1.0 + this.z) * 0.5;
            return this;
        },
    
        unproject: function (matrix, viewport) {
            Vector3.matrix = Vector3.matrix || new Matrix4();
            Vector3.matrix.getInverse(matrix);
    
            this.x = ((2.0 * this.x - viewport[0]) / viewport[2]) - 1.0;
            this.y = ((2.0 * this.y - viewport[1]) / viewport[3]) - 1.0;
            this.z = (2.0 * this.z) - 1.0;
    
            this.applyMatrix4(Vector3.matrix);
    
            return this;
        },
    
        transformDirection: function (m) {
            var x = this.x, y = this.y, z = this.z;
            var e = m.elements;
    
            this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z;
            this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z;
            this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z;
    
            return this.normalize();
        },
    
        divide: function (v) {
            this.x /= v.x;
            this.y /= v.y;
            this.z /= v.z;
    
            return this;
        },
    
        divideScalar: function (s) {
            return this.multiplyScalar(1 / s);
        },
    
        min: function (v) {
            this.x = Math.min(this.x, v.x);
            this.y = Math.min(this.y, v.y);
            this.z = Math.min(this.z, v.z);
    
            return this;
        },
    
        max: function (v) {
            this.x = Math.max(this.x, v.x);
            this.y = Math.max(this.y, v.y);
            this.z = Math.max(this.z, v.z);
    
            return this;
        },
    
        limit: function (scalar) {
            var len = this.lengthSq();
            if (len > scalar * scalar) {
                return this.multiplyScalar(scalar / Math.sqrt(len));
            }
            return this;
        },
    
        clamp: function (min, max) {
            this.x = Math.max(min.x, Math.min(max.x, this.x));
            this.y = Math.max(min.y, Math.min(max.y, this.y));
            this.z = Math.max(min.z, Math.min(max.z, this.z));
    
            return this;
        },
    
        clampScalar: function (min, max) {
            this.x = Math.max(min, Math.min(max, this.x));
            this.y = Math.max(min, Math.min(max, this.y));
            this.z = Math.max(min, Math.min(max, this.z));
    
            return this;
        },
    
        clampLength: function (min, max) {
            var length = this.length();
            return this.divideScalar(length || 1).multiplyScalar(Math.max(min, Math.min(max, length)));
        },
    
        negate: function () {
            this.x = - this.x;
            this.y = - this.y;
            this.z = - this.z;
    
            return this;
        },
        
        dot: function (v) {
            return this.x * v.x + this.y * v.y + this.z * v.z;
        },
    
        lengthSq: function () {
            return this.x * this.x + this.y * this.y + this.z * this.z;
        },
    
        length: function () {
            return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
        },
    
        manhattanLength: function () {
            return Math.abs(this.x ) + Math.abs( this.y ) + Math.abs( this.z);
        },
    
        normalize: function () {
            return this.divideScalar(this.length() || 1);
        },
    
        setLength: function (length) {
            return this.normalize().multiplyScalar(length);
        },
    
        cross: function (v) {
            return Vector3.cross(this, v, this);
        },
    
        projectOnVector: function (vector) {
            var scalar = vector.dot(this) / vector.lengthSq();
            return this.copy(vector).multiplyScalar(scalar);
        },
    
        projectOnPlane: function (planeNormal) {
            var vector = Vector3.vector || new Vector3();
            vector.copy(this).projectOnVector(planeNormal);
            return this.sub(vector);
        },
    
        reflect: function (normal) {
            var vector = this.vector || new Vector3();
            return this.sub(vector.copy(normal).multiplyScalar(2 * this.dot(normal)));
        },
    
        angleTo: function (v) {
            var theta = this.dot(v) / (Math.sqrt(this.lengthSq() * v.lengthSq()));
            return Math.acos(_Math.clamp(theta, -1, 1));
        },
    
        distanceTo: function (v) {
            return Math.sqrt(this.distanceToSquared(v));
        },
    
        distanceToSquared: function (v) {
            var dx = this.x - v.x, dy = this.y - v.y, dz = this.z - v.z;
            return dx * dx + dy * dy + dz * dz;
        },
    
        manhattanDistanceTo: function (v) {
            return Math.abs(this.x - v.x) + Math.abs(this.y - v.y) + Math.abs(this.z - v.z);
        },
    
        equals: function (v) {
            return (v.x === this.x) && (v.y === this.y) && (v.z === this.z);
        },
    
        toArray: function (array, offset) {
            if ( array === undefined ) array = [];
            if ( offset === undefined ) offset = 0;
    
            array[ offset ] = this.x;
            array[ offset + 1 ] = this.y;
            array[ offset + 2 ] = this.z;
    
            return array;
        }
    };
    
    Vector3.add = function (a, b, c) {
        c = c || new Vector3();
        c.x = a.x + b.x;
        c.y = a.y + b.y;
        c.z = a.z + b.z;
        return c;
    };
    
    Vector3.sub = function (a, b, c) {
        c = c || new Vector3();
        c.x = a.x - b.x;
        c.y = a.y - b.y;
        c.z = a.z - b.z;
        return c;
    };
    
    Vector3.multiply = function (a, b, c) {
        c = c || new Vector3();
        c.x = a.x * b.x;
        c.y = a.y * b.y;
        c.z = a.z * b.z;
        return c;
    };
    
    Vector3.divide = function (a, b, c) {
        c = c || new Vector3();
        c.x = a.x / b.x;
        c.y = a.y / b.y;
        c.z = a.z / b.z;
        return c;
    };
    
    Vector3.lerp = function (a, b, t, c) {
        var c = c || new Vector3();
        c.x = _Math.lerp(a.x, b.x, t);
        c.y = _Math.lerp(a.y, b.y, t);
        c.z = _Math.lerp(a.z, b.z, t);
        return c;
    };
    
    Vector3.smoothstep = function (a, b, t, c) {
        var c = c || new Vector3();
        c.x = _Math.smoothstep(a.x, b.x, t);
        c.y = _Math.smoothstep(a.y, b.y, t);
        c.z = _Math.smoothstep(a.z, b.z, t);
        return c;
    };
    
    Vector3.cross = function (a, b, c) {
        c = c || new Vector3();
        var ax = a.x, ay = a.y, az = a.z;
        var bx = b.x, by = b.y, bz = b.z;
    
        c.x = ay * bz - az * by;
        c.y = az * bx - ax * bz;
        c.z = ax * by - ay * bx;
        return c;
    };
    
    Vector3.fromArray = function (array, offset, c) {
        c = c || new Vector3();
        offset = offset || 0;
        c.x = array[offset];
        c.y = array[offset + 1];
        c.z = array[offset + 2];
        return c;
    };
    
    Vector3.fromMatrixColumn = function (m, index, c) {
        c = c || new Vector3();
        return Vector3.fromArray(m.elements, index * 4, c);
    };
    
    Vector3.fromMatrixPosition = function (m, c) {
        c = c || new Vector3();
        c.x = e[12];
        c.y = e[13];
        c.z = e[14];
    
        return c;
    };
    
    Vector3.fromMatrixScale = function (m, c) {
        var temp = Vector3.vector = Vector3.vector || new Vector3();
        c = c || new Vector3();
    
        var sx = Vector3.fromMatrixColumn(m, 0, temp).length();
        var sy = Vector3.fromMatrixColumn(m, 1, temp).length();
        var sz = Vector3.fromMatrixColumn(m, 2, temp).length();
        c.x = sx;
        c.y = sy;
        c.z = sz;
    
        return c;
    }
    
    Vector3.fromCylindrical = function (a, c) {
        c = c || new Vector3();
        c.x = a.radius * Math.sin(a.theta);
        c.y = a.y;
        c.z = a.radius * Math.cos(a.theta);
        return c;
    };
    
    Vector3.formSpherical = function (s, c) {
        c = c || new Vector3();
        c.x = sinPhiRadius * Math.sin( s.theta );
        c.y = Math.cos( s.phi ) * s.radius;
        c.z = sinPhiRadius * Math.cos( s.theta );
        return c;
    };
    
    Vector3.multiplyScalar = function (v, s, c) {
        c = c || new Vector3();
        c.x = v.x * s;
        c.y = v.y * s;
        c.z = v.z * s;
        return c;
    }

    App.define("vector3", {
        Vector3: Vector3
    })
})